package com.xyzwps.lib.ap;

import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import java.util.ArrayList;
import java.util.List;

public record TypeInfo(String typeName, String packageName,
                       DeclaredType type, TypeElement typeElement, Kind kind,
                       Modifiers modifiers,
                       List<FieldInfo> fields,
                       List<MethodInfo> methods,
                       List<ConstructorInfo> constructors) {

    public static TypeInfo of(TypeElement type) {
        var typeMirror = type.asType();
        if (typeMirror instanceof DeclaredType declaredType) {

            List<FieldInfo> fields = new ArrayList<>();
            List<MethodInfo> methods = new ArrayList<>();
            List<ConstructorInfo> constructors = new ArrayList<>();

            for (var element : type.getEnclosedElements()) {
                switch (element) {
                    case VariableElement v -> fields.add(FieldInfo.of(v));
                    case ExecutableElement e -> {
                        switch (element.getKind()) {
                            case METHOD -> methods.add(MethodInfo.of(e));
                            case CONSTRUCTOR -> constructors.add(ConstructorInfo.of(e));
                            default -> throw new IllegalStateException("Unexpected ExecutableElement: "
                                    + element.getKind());
                        }
                    }
                    default -> System.out.println("        - " + element + " " + element.getKind()
                            + " " + element.getClass().getCanonicalName() + " " + element.asType());
                }
            }


            return new TypeInfo(
                    type.getSimpleName().toString(),
                    ApUtils.packageName(typeMirror.toString()),
                    declaredType,
                    type,
                    TypeInfo.Kind.of(declaredType),
                    Modifiers.of(type.getModifiers()),
                    fields, methods, constructors
            );
        } else {
            throw new IllegalStateException("Unsupported TypeElement " + typeMirror);
        }
    }

    public String toString(int indent) {
        var is = "    ".repeat(Math.max(0, indent));

        var sb = new StringBuilder("TypeInfo:");
        sb.append('\n').append(is).append("  * typeName = ").append(typeName);
        sb.append('\n').append(is).append("  * packageName = ").append(packageName);
        sb.append('\n').append(is).append("  * type = ").append(type);
        sb.append('\n').append(is).append("  * kind = ").append(kind);
        sb.append('\n').append(is).append("  * modifiers = ").append(modifiers);

        if (!fields.isEmpty()) {
            sb.append('\n').append(is).append("  * fields：");
            for (var field : fields) {
                sb.append('\n').append(is).append("    - ").append(field);
            }
        }

        if (!methods.isEmpty()) {
            sb.append('\n').append(is).append("  * methods：");
            for (var method : methods) {
                sb.append('\n').append(is).append("    - ").append(method);
            }
        }

        if (!constructors.isEmpty()) {
            sb.append('\n').append(is).append("  * constructors：");
            for (var constructor : constructors) {
                sb.append('\n').append(is).append("    - ").append(constructor);
            }
        }

        return sb.toString();

    }

    public String canonicalName() {
        return packageName.isEmpty() ? typeName : (packageName + "." + typeName);
    }

    public enum Kind {
        CLASS,
        RECORD,
        INTERFACE;

        public static Kind of(DeclaredType type) {
            return Kind.of(type.asElement().getKind());
        }

        public static Kind of(ElementKind kind) {
            return switch (kind) {
                case CLASS -> CLASS;
                case INTERFACE -> INTERFACE;
                case RECORD -> RECORD;
                default -> null;
            };
        }
    }
}
